<script setup lang="ts">
import {
  computed,
  type CSSProperties,
  onMounted,
  onUnmounted,
  ref,
  watch,
  nextTick
} from 'vue'
import type FloatingProp from "@/components/floating/type/FloatingProp.ts"
import type FloatingEmit from "@/components/floating/type/FloatingEmit.ts"

const $emit = defineEmits<FloatingEmit>()

const $prop = withDefaults(defineProps<FloatingProp>(), {
  placement: 'bottom',
  disabled: false,
  trigger: 'click'
})

const floatingRef = ref<HTMLElement>()
const floatingContentRef = ref<HTMLElement>()

const computeSize = (size: string | number): string => {
  return typeof size === 'number' ? `${size}px` : size
}

// 样式对象（响应式）
const floatingStyle = ref<CSSProperties>({
  position: 'absolute',
  zIndex: 99999,
  top: '0px',
  left: '0px'
})

const updatePosition = () => {
  const referenceEl = floatingRef.value
  const contentEl = floatingContentRef.value
  if (!referenceEl || !contentEl) return

  const rect = referenceEl.getBoundingClientRect()
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop
  const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft

  let x = rect.left + scrollLeft
  let y = rect.bottom + scrollTop

  if ($prop.offset) {
    const [offsetX, offsetY] = Array.isArray($prop.offset) ? $prop.offset : [0, $prop.offset]
    x += offsetX
    y += offsetY
  }

  const style: CSSProperties = {
    position: 'absolute',
    zIndex: 99999,
    top: `${y}px`,
    left: `${x}px`,
    transform: 'none'
  }

  // 宽度和最大高度
  if ($prop.width) style.width = computeSize($prop.width)
  else style.width = referenceEl.offsetWidth + 'px'

  if ($prop.maxHeight) style.maxHeight = computeSize($prop.maxHeight)

  floatingStyle.value = style
}

watch(() => $prop.modelValue, (val) => {
  if (val) nextTick(updatePosition)
})

onMounted(() => {
  document.addEventListener('click', handleClickOutside)
  window.addEventListener('scroll', updatePosition, true)
  window.addEventListener('resize', updatePosition)
})

onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside)
  window.removeEventListener('scroll', updatePosition, true)
  window.removeEventListener('resize', updatePosition)
})

/**
 * 根据触发方式绑定事件
 */
const referenceEvents = computed(() => {
  const events: any = {}
  switch ($prop.trigger) {
    case 'click':
      events.onClick = () => toggle(!$prop.modelValue)
      break
    case 'hover':
      events.onMouseenter = () => toggle(true)
      events.onMouseleave = () => toggle(false)
      break
    case 'focus':
      events.onFocusin = () => toggle(true)
      events.onFocusout = () => {
        if ($prop.closeOnBlur) toggle(false)
      }
      break
    case 'custom':
      break
  }
  return events
})

const toggle = (newValue: boolean) => {
  $emit('update:modelValue', newValue)
}

// 点击外部关闭
const handleClickOutside = (event: MouseEvent) => {
  const target = event.target as Node
  if (
      floatingRef.value &&
      !floatingRef.value.contains(target) &&
      floatingContentRef.value &&
      !floatingContentRef.value.contains(target)
  ) {
    toggle(false)
  }
}

defineExpose({toggle})
</script>

<template>
  <div class="floating-container" ref="floatingRef">
    <div
        class="floating-obj"
        v-bind="referenceEvents"
        @mouseenter.stop
        @mouseleave.stop
        @focusin.stop
        @focusout.stop
    >
      <slot/>
    </div>
  </div>

  <!-- 浮动框始终挂载到 body，脱离层级遮挡 -->
  <Teleport to="body">
    <Transition name="fade">
      <div
          v-if="modelValue"
          class="floating-content"
          :style="floatingStyle"
          ref="floatingContentRef"
      >
        <slot name="floating"/>
      </div>
    </Transition>
  </Teleport>
</template>

<style scoped>
.floating-container {
  width: 100%;
  position: relative;
  display: inline-block;
}

.floating-obj {
  box-sizing: border-box;
}

.floating-content {
  background: white;
  border-radius: 3px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  overflow: auto;
  transform-origin: left top;
  /* 注意：位置和 z-index 已由 style 动态控制 */
}

@keyframes float-in {
  0% {
    opacity: 0;
    transform: scale(0.95);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes float-out {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(0.95);
  }
}

.fade-enter-active {
  animation: float-in 0.25s cubic-bezier(0.23, 1, 0.32, 1);
}

.fade-leave-active {
  animation: float-out 0.2s ease forwards;
}
</style>
